// prefetcher.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

char LICENSE[] SEC("license") = "GPL";

#define MAX_HISTORY 8 // 每个进程保留最近8次访问记录
#define DETECTION_THRESHOLD 3 // 连续3次步长相同则认为模式成立

// 记录单个进程的访问历史
struct access_history {
    u64 addresses[MAX_HISTORY]; // 循环数组记录地址
    s64 last_stride;            // 上一次计算出的步长
    u32 count;                  // 步长连续出现的次数
    u32 cursor;                 // 循环数组的当前指针
};

// 通过 Ring Buffer 发送给用户态代理的数据结构
struct pattern_info {
    u32 pid;
    u64 fault_addr;
    s64 stride;
    u32 count;
};

// 1. Per-CPU Hash Map 存储每个进程的访问历史
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, u32); // key: pid
    __type(value, struct access_history);
} history_map SEC(".maps");

// 2. Ring Buffer 用于向用户态发送事件
struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024); // 256 KB
} events SEC(".maps");

// 强制内联的辅助函数，用于更新历史并检测模式
static __always_inline void check_pattern(u32 pid, u64 addr) {
    struct access_history *history;
    history = bpf_map_lookup_elem(&history_map, &pid);
    
    if (!history) {
        // 首次访问，创建新条目
        struct access_history new_history = {};
        new_history.addresses[0] = addr;
        new_history.cursor = 1;
        new_history.count = 0;
        new_history.last_stride = 0;
        bpf_map_update_elem(&history_map, &pid, &new_history, BPF_ANY);
        return;
    }

    // 计算步长
    u32 prev_cursor = (history->cursor - 1 + MAX_HISTORY) % MAX_HISTORY;
    u64 prev_addr = history->addresses[prev_cursor];
    s64 current_stride = addr - prev_addr;

    if (current_stride > 0 && current_stride == history->last_stride) {
        history->count++;
    } else {
        // 步长变化，重置计数器
        history->count = 1;
    }
    
    history->last_stride = current_stride;
    history->addresses[history->cursor] = addr;
    history->cursor = (history->cursor + 1) % MAX_HISTORY;
    
    // 如果满足阈值，则上报
    if (history->count >= DETECTION_THRESHOLD) {
        struct pattern_info *info;
        info = bpf_ringbuf_reserve(&events, sizeof(*info), 0);
        if (!info) {
            return;
        }

        info->pid = pid;
        info->fault_addr = addr;
        info->stride = current_stride;
        info->count = history->count;

        bpf_ringbuf_submit(info, 0);
        
        // 上报后可以重置，避免重复上报
        history->count = 0; 
    }
}

// 3. 挂载到 page_fault_user 跟踪点
SEC("tracepoint/exceptions/page_fault_user")
int handle_page_fault(void *ctx) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u64 addr;

    // +++ 使用 bpf_probe_read_kernel 手动读取地址 +++
    // 我们从之前的 format 文件知道，'address' 字段在偏移量为 8 的位置
    // ctx 是指向追踪点数据开头的指针，我们加上偏移量 8，就得到了 address 字段的地址
    bpf_probe_read_kernel(&addr, sizeof(addr), (void *)ctx + 8);

    if (addr == 0) {
        return 0;
    }

    check_pattern(pid, addr);
    
    return 0;
}